package obecto.manager.dragManager
{
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.InteractiveObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TouchEvent;
	import flash.geom.Point;
	
	import obecto.event.DragEvent;
	import obecto.event.TaskEvent;
	import obecto.task.animation.AnimationTargetCacheAsBitmapPolicy;
	import obecto.task.animation.PropertiesTweenAnimation;
	import obecto.task.animation.PropertiesTweenAnimationType;
	import obecto.task.animation.TweenProperty;
	
	public class DragProxy extends Sprite
	{
		private static const RETURN_SPEED : Number = 0.0003; 
		
		private var startX:Number;
		private var startY:Number;
		private var startScaleX:Number;
		private var startScaleY:Number;
		
		private var returnOnCancel : Boolean;
		
		public var xOffset : Number;
		public var yOffset : Number;
		
		public var originOffsetX : Number;
		public var originOffsetY : Number;
		
		public var dragInitiator:DisplayObject;
		public var dragSource:Object;
		
		public function DragProxy(startX:Number = 0, 
								  startY:Number = 0, 
								  xOffset:Number = 0,
								  yOffset:Number = 0,
								  originOffsetX:Number = 0,
								  originOffsetY:Number = 0,
								  returnX:Number = 0,
								  returnY:Number = 0,
								  returnOnCancel:Boolean = true, 
								  startScaleX:Number = 1, 
								  startScaleY:Number = 1) : void
		{
			super();
			
			this.returnOnCancel = returnOnCancel;
			
			this.startX = returnX;
			this.startY = returnY;
			
			this.xOffset = xOffset;
			this.yOffset = yOffset;
			
			this.originOffsetX = originOffsetX;
			this.originOffsetY = originOffsetY;
			
			this.startScaleX = startScaleX;
			this.startScaleY = startScaleY;
			
			x = startX + xOffset;
			y = startY + yOffset;
			
			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
		}
		
		protected function onRemovedFromStage(event : Event) : void
		{
			removeListeners();
		}
		
		protected function onAddedToStage(event : Event) : void
		{
			addEventListeners();
		}
		
		protected function onTouchMove(event : TouchEvent) : void
		{
			onMoveActual(event);
		}
		
		protected function onMouseMove(event : MouseEvent) : void
		{
			onMoveActual(event);
		}
		
		public var target:DisplayObject = null;
		private var currentMouseTarget : DisplayObject;
		
		private function onMoveActual(event : Event):void
		{
			var targets : Array = []; 
			//if the drag events bubble, we can use the native mechanism, if they don't we need to use the patch.
			//DragProxy.getObjectsUnderPoint(parent, new Point(x-xOffset,y - yOffset), targets);
			targets = parent.getObjectsUnderPoint(new Point(x+originOffsetX,y+originOffsetY));
			
			var newTarget:DisplayObject = null;
			
			var targetIndex:int = targets.length - 1;
			while (targetIndex >= 0)
			{
				newTarget = targets[targetIndex];
				
				if (newTarget != this && !contains(newTarget))
				{
					break;
				}
				
				targetIndex--;
			}
			
			if (newTarget)
			{
				if (newTarget == target || 
                    (target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(newTarget)) ||
                    (newTarget == currentMouseTarget && target == null))
				{
					dispatchDragEvent(DragEvent.DRAG_OVER, event, target ? target : newTarget);
				}
				else
				{
					if (newTarget != currentMouseTarget)
					{
						if (target)
						{
							dispatchDragEvent(DragEvent.DRAG_EXIT, event, target);
						}
						
						target = null;
						currentMouseTarget = newTarget;
						dispatchDragEvent(DragEvent.DRAG_ENTER, event, newTarget);
					}
				}
			}
		}
		
		private function dispatchDragEvent(type:String, event:Event, eventTarget:Object):void
		{
			var dragEvent:DragEvent = new DragEvent(type);
			var pt:Point = new Point();
			
			dragEvent.dragInitiator = dragInitiator;
			dragEvent.dragSource = dragSource;
			dragEvent.dropSource = target;
			
			if (event is MouseEvent)
			{
				var mouseEvent : MouseEvent= event as MouseEvent;
				dragEvent.ctrlKey = mouseEvent.ctrlKey;
				dragEvent.altKey = mouseEvent.altKey;
				dragEvent.shiftKey = mouseEvent.shiftKey;
				pt.x = mouseEvent.localX;
				pt.y = mouseEvent.localY;
				pt = DisplayObject(mouseEvent.target).localToGlobal(pt);
			}
			else if (event is TouchEvent)
			{
				var touchEvent : TouchEvent= event as TouchEvent;
				pt.x = touchEvent.localX;
				pt.y = touchEvent.localY;
				pt = DisplayObject(touchEvent.target).localToGlobal(pt);
			}
			
			pt = DisplayObject(eventTarget).globalToLocal(pt);
			dragEvent.localX = pt.x;
			dragEvent.localY = pt.y;
			
			eventTarget.dispatchEvent(dragEvent);
		}
		
		protected function onTouchEnd(event : TouchEvent) : void
		{
			onMouseUpActual(event);
			removeListeners();
		}
		
		protected function onMouseUp(event : MouseEvent) : void
		{
			removeListeners();
			onMouseUpActual(event);
		}
		
		protected function onMouseUpActual(event : Event) : void
		{
			if (target)
			{
				dispatchDragEvent(DragEvent.DRAG_DROP, event, target);
				endDrag(event);
			}
			else
			{
				if (returnOnCancel)
				{
					returnImage();	
				}
			}
		}
		
		private function endDrag(event : Event):void
		{
			if (dragInitiator)
			{
				dispatchDragEvent(DragEvent.DRAG_COMPLETE, event, dragInitiator);
			}
			DragManager.instance.stopDrag();
		}
		
		private function returnImage():void
		{
			var move : PropertiesTweenAnimation = new PropertiesTweenAnimation();
			move.target = this;
			move.tweenType = PropertiesTweenAnimationType.TIME_TWEEN;
			move.targetCacheAsBitmapPolicy = AnimationTargetCacheAsBitmapPolicy.LEAVE;
			move.applyStartValuesOnStart = true;
			
			var returnDurationQuotioent:Number = Math.sqrt(Math.pow((x - startX - xOffset), 2) + Math.pow((y - startY - yOffset), 2));
			move.duration = returnDurationQuotioent * RETURN_SPEED;
			
			var properties : Array = [];
			
			properties.push(initializeTweenProperty("x", x, startX + xOffset));
			properties.push(initializeTweenProperty("y", y, startY + yOffset));
			properties.push(initializeTweenProperty("scaleX", scaleX, startScaleX));
			properties.push(initializeTweenProperty("scaleY", scaleY, startScaleY));
			
			move.tweeningProperties = properties;
			
			removeListeners();
			move.addEventListener(TaskEvent.COMPLETE, onReturnComplete);
			move.start();
			
		}
		
		private function initializeTweenProperty(name:String, startValue:Number, endValue:Number):TweenProperty
		{
			var property : TweenProperty = new TweenProperty();
			property.name = name;
			property.startValue = startValue;
			property.endValue = endValue;
			return property;
		}
		
		protected function onReturnComplete(event:Event):void
		{
			PropertiesTweenAnimation(event.target).removeEventListener(TaskEvent.COMPLETE, onReturnComplete);
			endDrag(event);
		}
		
		private function addEventListeners() : void
		{
			stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
			stage.addEventListener(TouchEvent.TOUCH_END, onTouchEnd);
			
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
			stage.addEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
		}
		
		private function removeListeners() : void
		{
			stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
			stage.removeEventListener(TouchEvent.TOUCH_END, onMouseUp);
			
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
			stage.removeEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
		}
		
		private static function getObjectsUnderPoint(obj:DisplayObject, pt:Point, arr:Array):void
		{
			if (!obj.visible)
				return;
			
			if (obj.hitTestPoint(pt.x, pt.y, false))
			{
				if (obj is InteractiveObject && InteractiveObject(obj).mouseEnabled)
				{
					arr.push(obj);
				}
				
				if (obj is DisplayObjectContainer)
				{
					var doc:DisplayObjectContainer = obj as DisplayObjectContainer;
					if (doc.mouseChildren)
					{
						// we use this test so we can test in other application domains
						if ("rawChildren" in doc)
						{
							var rc:Object = doc["rawChildren"];
							n = rc.numChildren;
							for (i = 0; i < n; i++)
							{
								try
								{
									getObjectsUnderPoint(rc.getChildAt(i), pt, arr);
								}
								catch (e:Error)
								{
									//another sandbox?
								}
							}
						}
						else
						{
							if (doc.numChildren)
							{
								var n:int = doc.numChildren;
								for (var i:int = 0; i < n; i++)
								{
									try
									{
										var child:DisplayObject = doc.getChildAt(i);
										getObjectsUnderPoint(child, pt, arr);
									}
									catch (e:Error)
									{
										//another sandbox?
									}
								}
							}
						}
					}
				}
			}
		}
	}
}